<?php
/*
* Kusht Mud.
* ----
* The MIT License
* Copyright (c) 2010 Andrew Tutt
* ----
*/

/* Chess Class
*
* This is the MiniGame which allows players to play Chess
*
*/

define("WHITE", "w");
define("BLACK", "b");

define("A", 1);
define("B", 2);
define("C", 3);
define("D", 4);
define("E", 5);
define("F", 6);
define("G", 7);
define("H", 8);

class position {
	public $row;
	public $col;
	function __construct($col,$row) {
		$this->row = $row;
		$this->col = $col;
	}
}

class ChessPiece {
	public static $moves_executed = 0;

	public $name;
	public $color;
	public $image;
	
	public $pastmoves;
	public $currentposition;
	
	public $firstmove = true;
	
	function __construct($color,$position) {
		$this->color = $color;
		$this->name = get_class($this);
		$this->pastmoves = array();
		$this->currentposition = $position;
	}
}


class Rook extends ChessPiece {
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'r.png';
	}
	
	function move($position,$board) {
		$op = $this->currentposition;
		$np = $position;
		
		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }
		
		if($np->row === $op->row) { // column
			$e = abs($op->col-$np->col);
			$mc = 1 - 2*($op->col-$np->col > 0);
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col+$mc*$i][$op->row],"ChessPiece")) { return false; }
			}
			return true;
		}
		
		if($np->col === $op->col) { // row
			$e = abs($op->row-$np->row);
			$mr = 1 - 2*($op->row-$np->row > 0); // 1 or -1;
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col][$op->row+$mr*$i],"ChessPiece")) { return false; }
			}
			return true;
		}
		
		return false;
	}
}

class Knight extends ChessPiece {
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'n.png';
	}

	function move($position,$board) {
		$op = $this->currentposition;
		$np = $position;
		
		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }
		
		if(abs($op->row-$np->row)===1 xor abs($op->col-$np->col)===1) {
			if(abs($op->row-$np->row)===2 xor abs($op->col-$np->col)===2) {
				return true;
			}
		}
		
		return false;
	}	
}

class Bishop extends ChessPiece {
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'b.png';
	}

	function move($position,$board) {
		$op = $this->currentposition;
		$np = $position;
		
		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }

		if(abs($op->row-$np->row) === abs($op->col-$np->col)) { // diag
			$e = abs($op->row-$np->row);
			$mr = 1 - 2*($op->row-$np->row > 0); // 1 or -1;
			$mc = 1 - 2*($op->col-$np->col > 0);
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col+$mc*$i][$op->row+$mr*$i],"ChessPiece")) { return false; }
			}
			return true;
		}
		
		return false;
	}	
}

class Queen extends ChessPiece {
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'q.png';
	}

	function move($position,$board) {
		$op = $this->currentposition;
		$np = $position;

		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }

		if(abs($op->row-$np->row) === abs($op->col-$np->col)) { // diag
			$e = abs($op->row-$np->row);
			$mr = 1 - 2*($op->row-$np->row > 0); // 1 or -1;
			$mc = 1 - 2*($op->col-$np->col > 0);
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col+$mc*$i][$op->row+$mr*$i],"ChessPiece")) { return false; }
			}
			return true;
		}
		
		if($np->row === $op->row) { // column
			$e = abs($op->col-$np->col);
			$mc = 1 - 2*($op->col-$np->col > 0);
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col+$mc*$i][$op->row],"ChessPiece")) { return false; }
			}
			return true;
		}
		
		if($np->col === $op->col) { // row
			$e = abs($op->row-$np->row);
			$mr = 1 - 2*($op->row-$np->row > 0); // 1 or -1;
			for($i = 1; $i < $e; $i++) {
				if($i==$e && $i==2) { continue; }
				if(is_a($board[$op->col][$op->row+$mr*$i],"ChessPiece")) { return false; }
			}
			return true;
		}
		
	
		return false;
	}
}

class King extends ChessPiece {
	public $in_check;
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'k.png';
		$this->in_check = false;
	}

	function move($position,&$board) {
		$op = $this->currentposition;
		$np = $position;

		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }
		
		if(abs($op->row-$np->row) <= 1 && abs($op->col-$np->col) <= 1) {
			return true;
		}
		
		// Castle
		if($this->firstmove===true && ((is_a($board[$op->col+3][$op->row],"ChessPiece") && $board[$op->col+3][$op->row]->firstmove===true) || 
		(is_a($board[$op->col-4][$op->row],"ChessPiece") && $board[$op->col-4][$op->row]->firstmove===true)) && 
		($np->row===$op->row && ($op->col+2===$np->col xor $op->col-2===$np->col)) ) {
			$e = abs($op->col-$np->col);
			$mc = 1 - 2*($op->col-$np->col > 0); // 1 or -1;
			for($i = 1; $i < $e; $i++) {
				if(is_a($board[$op->col+$mc*$i][$op->row],"ChessPiece")) { return false; }
			}
			
			// TODO: If it would put it in check, return false
			
			
			if($mc > 0) {
				$board[$op->col+1][$op->row] = $board[$op->col+3][$op->row];
				$board[$op->col+1][$op->row]->currentposition=new position($op->col+1,$op->row);
				$board[$op->col+3][$op->row] = DEF_NEG;
			} else {
				$board[$op->col-1][$op->row] = $board[$op->col-4][$op->row];
				$board[$op->col-1][$op->row]->currentposition=new position($op->col-1,$op->row);
				$board[$op->col-4][$op->row] = DEF_NEG;
			}
			
			return true; 
		}
		
		return false;
	}	
}

class Pawn extends ChessPiece {
	function __construct($color,$position) {
		parent::__construct($color,$position);
		$this->image = $this->color . 'p.png';
	}

	function move($position,&$board) {
		$uod = $this->color===BLACK ? -1 : 1; // UpOrDown
		$op = $this->currentposition;
		$np = $position;
		
		if(is_a($board[$np->col][$np->row],"ChessPiece") && $board[$np->col][$np->row]->color===$this->color) { return false; }
		if($op->row+$uod > 8 || $op->row+$uod < 1) { return false; }
		
		if($op->row+$uod === $np->row && $op->col===$np->col && !is_a($board[$np->col][$np->row],"ChessPiece")) {
			return true;
		}
		
		if($op->row+$uod === $np->row && 
		(($op->col+1===$np->col && is_a($board[$op->col+1][$np->row],"ChessPiece")) || 
		($op->col-1===$np->col && is_a($board[$op->col-1][$np->row],"ChessPiece")))) {
			return true;
		}
		
		if($op->row+$uod === $np->row) {
			if ($op->col+1===$np->col && is_a($board[$op->col+1][$op->row],"ChessPiece") && 
			array_key_exists(ChessPiece::$moves_executed,$board[$op->col+1][$op->row]->pastmoves)
			&& $board[$op->col+1][$op->row]->pastmoves[ChessPiece::$moves_executed]->row===(int)fmod(9+7*$uod,9)) {
				
				$board[$op->col+1][$op->row] = DEF_NEG;
				return true;
			}
		
			if ($op->col-1===$np->col && is_a($board[$op->col-1][$op->row],"ChessPiece") && 
			array_key_exists(ChessPiece::$moves_executed,$board[$op->col-1][$op->row]->pastmoves)
			&& $board[$op->col-1][$op->row]->pastmoves[ChessPiece::$moves_executed]->row===(int)fmod(9+7*$uod,9)) {
				
				$board[$op->col-1][$op->row] = DEF_NEG;
				return true;
			}
		}
		
		if($op->row+$uod*2 === $np->row && $op->col===$np->col && $this->firstmove 
		&& !is_a($board[$np->col][$np->row],"ChessPiece") && !is_a($board[$np->col][$op->row+$uod],"ChessPiece")) {
			return true;
		}
		
		return false;
	}
}

class ChessGames {
	public static $currentgames = array();
	public static $top_id = 0;
}

class ChessGame extends saveable {
	public $gameid; // Keep it real
	public $plrW;
	public $plrB;
	public $plrW_King;
	public $plrB_King;
	
	public $BoardState = array();
	public $on_turn;
	public $off_turn;
	
	function __construct($plrW,$plrB) {
		$this->gameid = ChessGames::$top_id++;
		ChessGames::$currentgames += array($this->gameid=>$this);
		
		$this->plrW = $plrW;
		$this->plrB = $plrB;
		$this->on_turn = $plrW;
		$this->off_turn = $plrB;
	
		$this->BoardState[A] = array_fill(0,8,DEF_NEG);
		$this->BoardState[B] = array_fill(0,8,DEF_NEG);
		$this->BoardState[C] = array_fill(0,8,DEF_NEG);
		$this->BoardState[D] = array_fill(0,8,DEF_NEG);
		$this->BoardState[E] = array_fill(0,8,DEF_NEG);
		$this->BoardState[F] = array_fill(0,8,DEF_NEG);
		$this->BoardState[G] = array_fill(0,8,DEF_NEG);
		$this->BoardState[H] = array_fill(0,8,DEF_NEG);
	
		$this->BoardState[A][1] = new Rook(WHITE,new position(A,1));
		$this->BoardState[A][2] = new Pawn(WHITE,new position(A,2));
		$this->BoardState[A][7] = new Pawn(BLACK,new position(A,7));
		$this->BoardState[A][8] = new Rook(BLACK,new position(A,8));
		
		$this->BoardState[B][1] = new Knight(WHITE,new position(B,1));
		$this->BoardState[B][2] = new   Pawn(WHITE,new position(B,2));
		$this->BoardState[B][7] = new   Pawn(BLACK,new position(B,7));
		$this->BoardState[B][8] = new Knight(BLACK,new position(B,8));
		
		$this->BoardState[C][1] = new Bishop(WHITE,new position(C,1));
		$this->BoardState[C][2] = new   Pawn(WHITE,new position(C,2));
		$this->BoardState[C][7] = new   Pawn(BLACK,new position(C,7));
		$this->BoardState[C][8] = new Bishop(BLACK,new position(C,8));
		
		$this->BoardState[D][1] = new Queen(WHITE,new position(D,1));
		$this->BoardState[D][2] = new  Pawn(WHITE,new position(D,2));
		$this->BoardState[D][7] = new  Pawn(BLACK,new position(D,7));
		$this->BoardState[D][8] = new Queen(BLACK,new position(D,8));
		
		$this->BoardState[E][1] = $this->plrW_King = new King(WHITE,new position(E,1));
		$this->BoardState[E][2] = new Pawn(WHITE,new position(E,2));
		$this->BoardState[E][7] = new Pawn(BLACK,new position(E,7));
		$this->BoardState[E][8] = $this->plrB_King = new King(BLACK,new position(E,8));
		
		$this->BoardState[F][1] = new Bishop(WHITE,new position(F,1));
		$this->BoardState[F][2] = new   Pawn(WHITE,new position(F,2));
		$this->BoardState[F][7] = new   Pawn(BLACK,new position(F,7));
		$this->BoardState[F][8] = new Bishop(BLACK,new position(F,8));
		
		$this->BoardState[G][1] = new Knight(WHITE,new position(G,1));
		$this->BoardState[G][2] = new   Pawn(WHITE,new position(G,2));
		$this->BoardState[G][7] = new   Pawn(BLACK,new position(G,7));
		$this->BoardState[G][8] = new Knight(BLACK,new position(G,8));
		
		$this->BoardState[H][1] = new Rook(WHITE,new position(H,1));
		$this->BoardState[H][2] = new Pawn(WHITE,new position(H,2));
		$this->BoardState[H][7] = new Pawn(BLACK,new position(H,7));
		$this->BoardState[H][8] = new Rook(BLACK,new position(H,8));
		
		$this->update_players();
	}
	
	function move($fcol,$frow,$tcol,$trow,$plr) {
		if($plr === $this->off_turn) { // Move off turn? No.
			$plr->send("It's not your turn.");
			return false; 		
		}
		if($tcol > 8 || $trow > 8) 	{  // Move off the board? No.
			$plr->send("That location is not on the board.");
			return false; 
		} 
		if($fcol===$frow && $tcol===$trow)	{ // Move to current position? No.
			$plr->send("That's where the piece is already.");			
			return false; 
		} 
		if(!is_a($this->BoardState[$fcol][$frow],"ChessPiece")) { // Move a non-piece? No.
			$plr->send("You don't have any pieces there.");
			return false; 
		} 
		if(($this->BoardState[$fcol][$frow]->color===WHITE && $plr===$this->plrB) || // Move opponent's piece? No.
		($this->BoardState[$fcol][$frow]->color===BLACK && $plr===$this->plrW)) {
			$plr->send("You cannot move your opponent's piece."); 
			return false; 
		} 
		if(is_a($this->BoardState[$tcol][$trow],"ChessPiece") // Move over your own piece? No.
		&& $this->BoardState[$tcol][$trow]->color===$this->BoardState[$fcol][$frow]->color) { 
			$plr->send("You have attempted to move one of your pieces on top of another one of your pieces.");
			return false; 
		} 
		
		if($this->on_turn === $this->plrW) {$king = $this->plrW_King;} else {$king = $this->plrB_King;}
		if($this->would_expose_check($this->BoardState,$king,(int)$fcol,(int)$frow,(int)$tcol,(int)$trow)) {
			$plr->send("That move would expose your king to check.");
			return false; 		
		}
		
		if($this->on_turn === $this->plrW) {$king = $this->plrB_King;} else {$king = $this->plrW_King;}
		
		$p = new position((int)$tcol,(int)$trow);
		if($this->BoardState[$fcol][$frow]->move($p,$this->BoardState)) {
			$this->do_move($this->BoardState[$fcol][$frow],$p);
			$king->in_check = $this->place_check($king);
			$this->update_players();
			
			if($king->in_check) {
				// Check for the win...
				if($this->is_checkmate($this->BoardState,$king)) {
					$this->off_turn->send("You were checkmated.");
					$this->on_turn->send("CHECKMATE! You win!");
					$this->endGame();
				}
			}
			
			$king->in_check = false; // must be true w/o checkmate
			$this->turn();
			return true;
		} else {
			$plr->send("You can't make that move.");
			return false; 		
		}
	}
	
	function do_move($piece,$position) {
		// Promote Pawns
		if(is_a($piece,"Pawn") && ($position->row===8 || $position->row===1)) {
			$tmp = new Queen($piece->color,$piece->currentposition);
			$tmp->pastmoves = $piece->pastmoves;
			$piece = $tmp;
		}
	
		$piece->firstmove = false;
		ChessPiece::$moves_executed++;
		$piece->pastmoves[ChessPiece::$moves_executed] = $piece->currentposition;
		$this->BoardState[$piece->currentposition->col][$piece->currentposition->row] = DEF_NEG;
		$piece->currentposition = $position;
		$this->BoardState[$piece->currentposition->col][$piece->currentposition->row] = $piece;
	}
	
	function place_check(&$king) {
		foreach($this->BoardState as $col) {
			foreach($col as $element) {
				if(is_a($element,"ChessPiece") && $element->color!==$king->color) {
					if($this->BoardState[$element->currentposition->col][$element->currentposition->row]->move($king->currentposition,$this->BoardState)) {
						$this->off_turn->send("Check.");
						$this->on_turn->send("Check.");
						return true;
					}
				}
			}
		}
		
		return false;
	}

	function would_expose_check($board,$king,$fcol,$frow,$tcol,$trow) {
		$piece = $board[$fcol][$frow];
		$board[$fcol][$frow] = DEF_NEG;
		$board[$tcol][$trow] = $piece;
		$p = $king->currentposition;
		if($piece===$king) { $p = new position($tcol,$trow); }
	
		foreach($board as $column) {
			foreach($column as $element) {
				if(is_a($element,"ChessPiece") && $element->color!==$king->color) {
					//echo $element->currentposition->col." ".$element->currentposition->row."\n";
					if($board[$element->currentposition->col][$element->currentposition->row]->move($p,$board)) {
						return true;
					}
				}
			}
		}	
		
		return false;	
	}
	
	function is_checkmate($board,$king) {
		$kcpr = $king->currentposition->row;
		$kcpc = $king->currentposition->col;
		
		// Wiggle the king
		for($i=$kcpc-1;$i<=$kcpc+1;$i++) {
			if($i > 8 || $i < 1) { continue; }
			for($j=$kcpr-1;$j<=$kcpr+1;$j++) {
				if($j > 8 || $j < 1) { continue; }
				if($king->move(new position($i,$j),$board) && !$this->would_expose_check($board,$king,$kcpc,$kcpr,$i,$j)) { return false; }
			}
		}
		
		// Kill or Block the checking piece
		// We literally check every possible move by every piece on the king's side because it's 6 am 
		// and I know this will work and computer chips are very fast
		foreach($board as $col) {
			foreach($col as $element) {
				if(is_a($element,"ChessPiece") && $element->color===$king->color) {
					for($c=1;$c<=8;$c++) {
						for($r=1;$r<=8;$r++) {
							if($element->move(new position($c,$r),$board) &&
							!$this->would_expose_check($board,$king,$element->currentposition->col,$element->currentposition->row,$c,$r)) {
								return false;
							}
						}
					}
				}
			}
		}	
		
		return true;
	}
	
	function turn() {
		$tmp = $this->on_turn;
		$this->on_turn = $this->off_turn;
		$this->off_turn = $tmp;
	}
	
	function update_players() {
		ClientHandler::sendChess($this->plrW,$this);
		ClientHandler::sendChess($this->plrB,$this);
	}
	
	function in_game($plr) {
		return ($this->plrW===$plr || $this->plrB===$plr);
	}
	
	function quitGame() {
		$this->endGame();
	}
	
	function endGame() {
		$this->plrW->rmv_bit(PLAYING_CHESS);
		$this->plrB->rmv_bit(PLAYING_CHESS);
		if($this->plrW->isnt(STANDING)) { Commands::stand($this->plrW,""); }
		if($this->plrB->isnt(STANDING)) { Commands::stand($this->plrB,""); }
		unset($this);
		// Something
	}
}


?>